#ifndef CUDA_EPI_MODEL_H
#define CUDA_EPI_MODEL_H

#include "../epimodel.h"
#include "../epimodelparameters.h"
#include "CudaRand.h"
#include <thrust/device_vector.h>

struct TestPpl{
	__device__
	void operator()(Person& p){
		
	}
};

template <class T>
__device__ inline T dmin(T a, T b){
	return (a<b)?a:b;
};

void cudaInitialize(EpiModel& input_model);
void copyCommunity(bool host_to_device);
bool compareModel(EpiModel& compare_model); // compare if two models are the same (cuda and flute)
void clean();

struct dCommunity{
	unsigned int id;	
	int nTractID;
	unsigned int nFirstPerson,nLastPerson;	
	int nNumWorkGroups;
	double daycpcm[5];
	double daycpnh[5];
	double cpcm[5];        // community-specific community contact rates
	double cpnh[5];        // community-specific neighborhood contact rates
	double cps[10];
	int ninf[TAG];
	int nEverInfected[TAG];
	int nsym[TAG];
	int nEverSymptomatic[TAG];
	int nEverAscertained[TAG]; // number of residents ever ascertained
	
	dCommunity(){}
	
	void copyToCommunity(Community& comm){
		comm.id = id;
		comm.nTractID = nTractID;
		comm.nFirstPerson = nFirstPerson;
		comm.nLastPerson = nLastPerson;
		comm.nNumWorkGroups = nNumWorkGroups;
		for(int i = 0; i < 5; i++){
			comm.daycpcm[i] = daycpcm[i];
			comm.daycpnh[i]=daycpnh[i];
			comm.cpcm[i] = cpcm[i];
			comm.cpnh[i] = cpnh[i];
		}
		for(int i = 0; i < 10; i++){comm.cps[i] = cps[i];}	
		for(int i = 0; i < TAG; i++){
			comm.ninf[i] = ninf[i];
			comm.nEverInfected[i] = nEverInfected[i];
			comm.nsym[i]=nsym[i];
			comm.nEverSymptomatic[i] = nEverSymptomatic[i];
			comm.nEverAscertained[i] = nEverAscertained[i];
		}
	}
	
	void copyFromCommunity(Community& comm){
		id = comm.id;
		nTractID = comm.nTractID;
		nFirstPerson = comm.nFirstPerson;
		nLastPerson = comm.nLastPerson;
		nNumWorkGroups = comm.nNumWorkGroups;
		for(int i = 0; i < 5; i++){
			daycpcm[i] = comm.daycpcm[i];
			daycpnh[i]= comm.daycpnh[i];
			cpcm[i] = comm.cpcm[i];
			cpnh[i] = comm.cpnh[i];
		}
		for(int i = 0; i < 10; i++){cps[i] = comm.cps[i];}
		for(int i = 0; i < TAG; i++){
			ninf[i] = comm.ninf[i];
			nEverInfected[i] = comm.nEverInfected[i];
			nsym[i]=comm.nsym[i];
			nEverSymptomatic[i] = comm.nEverSymptomatic[i];
			nEverAscertained[i] = comm.nEverAscertained[i];
		}
	}
	
	dCommunity(Community& comm){
		id = comm.id;
		nTractID = comm.nTractID;
		nFirstPerson = comm.nFirstPerson;
		nLastPerson = comm.nLastPerson;
		nNumWorkGroups = comm.nNumWorkGroups;
		for(int i = 0; i < 5; i++){
			daycpcm[i] = comm.daycpcm[i];
			daycpnh[i]= comm.daycpnh[i];
			cpcm[i] = comm.cpcm[i];
			cpnh[i] = comm.cpnh[i];
		}
		for(int i = 0; i < 10; i++){cps[i] = comm.cps[i];}
		for(int i = 0; i < TAG; i++){
			ninf[i] = comm.ninf[i];
			nEverInfected[i] = comm.nEverInfected[i];
			nsym[i]=comm.nsym[i];
			nEverSymptomatic[i] = comm.nEverSymptomatic[i];
			nEverAscertained[i] = comm.nEverAscertained[i];
		}
	}
};
const static int dTARGETCOMMUNITYSIZE = 2000;
const static int dFAMILIESPERCLUSTER = 4;
const static int dWORKGROUPSIZE = 20;

struct dCommWorker{
	unsigned int comm_id; // its correspoding coummunity id
	unsigned int worker_id; // id of the person who is working with community (non-resident)

	dCommWorker(){}

	dCommWorker(unsigned int _comm_id, unsigned int _worker_id){
		comm_id = _comm_id; worker_id = _worker_id;		
	}
	__device__
	bool operator<(const dCommWorker other) const{
		return comm_id < other.comm_id;
	}
};

struct dCommVisitor{
	unsigned int comm_id; // its correspoding coummunity id
	Person visitor; // id of the person who is visiting this community
	
	dCommVisitor(){}
	
	dCommVisitor(unsigned int _comm_id, Person _visitor){
		comm_id = _comm_id; visitor = _visitor;
	}
	
	__device__
	bool operator<(const dCommVisitor other) const{
		return comm_id < other.comm_id;
	}
};

/*********** model parameters ***********/
__device__ __constant__ double dvload[VLOADNSUB][VLOADNDAY];
__device__ __constant__ double dseasonality[MAXRUNLENGTH];
__device__ __constant__ double dfVaccineEfficacyByAge[TAG];
__device__ __constant__ vaccinedatastruct dVaccineData[NUMVACCINES];
__device__ __constant__ double dcpfc[TAG];
__device__ __constant__ double dcpfa[TAG];
__device__ __constant__ double dcphcc[TAG];
__device__ __constant__ double dcphca[TAG];
__device__ __constant__ unsigned int dwithdrawcdf32[3][WITHDRAWDAYS];
__device__ __constant__ unsigned int dincubationcdf32[3];
__device__ __constant__ double dcps[10];
__device__ __constant__ double d_travel_length_cdf[12];
__device__ __constant__ double d_travel_pr[TAG];
__device__ __constant__ int dnSchoolOpeningDays[56];
__device__ __constant__ unsigned char dnVaccinePriorities2[PRIORITY_LAST];

#define dnQuarantineLength 7
#define ddefaultboostday 21
#define dnAntiviralCourseSize 10
#define dfStopAntiviralTwoPills 0.05

__device__ const double ddefaultvacceff[VACCEFFLENGTH+1] = 
{0,0.001,0.004,0.011,0.023,0.043,0.07,0.106,0.153,0.211,0.28,0.363,0.46,0.572,0.7,0.7,0.7,0.7,0.7,0.7,0.7,0.7,0.702,0.71,0.73,0.766,0.82,0.897,1}; // initially in epimodel.cpp, since it is not in header, so I copied here


/********** infection functions *******/
struct InfectOp{	
	RandGen* rand_vec;
	int timer;	
	double AVEp;
	double fSymptomaticAscertainment;
	int bTrigger;
	double fLiberalLeaveCompliance;
	double fIsolationCompliance;
	
	dCommunity* comm_vec;
	Person* pvec;
	
	InfectOp(
		RandGen* _rand_vec,
		int _timer,	
		double _AVEp,
		double _fSymptomaticAscertainment,
		int _bTrigger,
		double _fLiberalLeaveCompliance,
		double _fIsolationCompliance,
		dCommunity* _comm_vec,
		Person* _pvec
		){
		
		rand_vec = _rand_vec;
		timer = _timer;	
		AVEp = _AVEp;
		fSymptomaticAscertainment = _fSymptomaticAscertainment;
		bTrigger = _bTrigger;
		fLiberalLeaveCompliance = _fLiberalLeaveCompliance;
		fIsolationCompliance = _fIsolationCompliance;
		comm_vec = _comm_vec;
		pvec = _pvec;
	}

	__device__ 
	void operator()(int& pid){
		Person& p = pvec[pid];
	
		RandGen rand_obj = rand_vec[p.id];
		p.nInfectedTime = timer;	
		clearSusceptible(p); // no longer susceptible
	    setInfected(p);           // infected
		// length of incubation period
	    p.iday=-1;  // set to -1 so the person is not infectious until tomorrow
		p.ibits = 0;
	    double fSymptomaticProb=0.67;
		if (isVaccinated(p)) {
            if (needsBoost(p))
                fSymptomaticProb*=(1.0-dVaccineData[whichVaccine(p)].VEp*
                ddefaultvacceff[p.vday]*dfVaccineEfficacyByAge[p.age]);
            else
                fSymptomaticProb*=(1.0-dVaccineData[whichVaccine(p)].VEp*
                dVaccineData[whichVaccine(p)].vacceff[p.vday]*
	            dfVaccineEfficacyByAge[p.age]);
        }
		
		if (isAntiviral(p)) fSymptomaticProb*=(1.0-AVEp);

		double rn = rand_obj.cudaRandUni();
		if (rn<fSymptomaticProb) {  // will be symptomatic
			setWillBeSymptomatic(p);
			unsigned int rn32 = rand_obj.cudaRandInt();
			if (rn32 < dincubationcdf32[0])
				setIncubationDays(p,1);
			else if (rn32 < dincubationcdf32[1])
				setIncubationDays(p,2);
			else
				setIncubationDays(p,3);
			if (rn<fSymptomaticProb*fSymptomaticAscertainment) { // will be ascertained
				setWillBeAscertained(p);
			}

			rn32 = rand_obj.cudaRandInt();
			unsigned int *wdcdf = dwithdrawcdf32[isChild(p)?p.age:2];
			if (rn32<wdcdf[0])
				setWithdrawDays(p,getIncubationDays(p)+0);
			else if (rn32<wdcdf[1])
				setWithdrawDays(p,getIncubationDays(p)+1);
			else if (rn32<wdcdf[2])
				setWithdrawDays(p,getIncubationDays(p)+2);
			else
				setWithdrawDays(p,0); // will not withdraw


			if (bTrigger && (getWithdrawDays(p)==0 || // doesn't voluntarily withdraw
			getWithdrawDays(p)-getIncubationDays(p)>1)) { // would withdraw later
				if ((fLiberalLeaveCompliance > 0.0 && isWorkingAge(p) && p.nWorkplace > 0
				&& rand_obj.cudaRandUni() < fLiberalLeaveCompliance) || // on liberal leave
				(fIsolationCompliance > 0.0 && rand_obj.cudaRandUni() < fIsolationCompliance)){ 
					setWithdrawDays(p,getIncubationDays(p)+1);
				}
			}
		}else{                
			// will NOT be symptomatic
            setIncubationDays(p,0);
            setWithdrawDays(p,0);   // note: withdraw days is only checked when symptomatic
		}
		if (p.nTravelTimer<=0) {
			atomicAdd(&(comm_vec[p.nHomeComm].ninf[p.age]),1);
            atomicAdd(&(comm_vec[p.nHomeComm].nEverInfected[p.age]),1);
        }
		
		rand_vec[p.id] = rand_obj;
	}
};

/*********** day functions *************/
void cudaDay();

// calculate daytime susceptibility and infectiousness for each person
struct DayOp1{
	int timer;
	double AVEs,AVEi;

	DayOp1(int _timer,double _AVEs, double _AVEi){
		timer = _timer;
		AVEs = _AVEs; AVEi = _AVEi;
	}

	__device__
	void operator()(Person& p){

		p.prs = 1.0 - p.fBaselineVES;
		if(isInfectious(p)) 
			p.pri = dvload[p.nWhichVload][(int)(p.iday)] * dseasonality[timer/2]; 
		else 
			p.pri = 0.0;
		if(isVaccinated(p)){
			if (needsBoost(p)){
			p.prs *= (1.0-dVaccineData[whichVaccine(p)].VEs*
				ddefaultvacceff[p.vday]*dfVaccineEfficacyByAge[p.age]);
			p.pri *= (1.0-dVaccineData[whichVaccine(p)].VEi*
				ddefaultvacceff[p.vday]*dfVaccineEfficacyByAge[p.age]);
      			} else {
			p.prs *= (1.0-dVaccineData[whichVaccine(p)].VEs*
				dVaccineData[whichVaccine(p)].vacceff[p.vday]*
				dfVaccineEfficacyByAge[p.age]);
			p.pri *= (1.0-dVaccineData[whichVaccine(p)].VEi*
				dVaccineData[whichVaccine(p)].vacceff[p.vday]*
				dfVaccineEfficacyByAge[p.age]);
      			}
		}
		if(isAntiviral(p)){
			p.prs *= (1.0-AVEs);
      		p.pri *= (1.0-AVEi);
		}
		if (isSymptomatic(p)) p.pri *= 2.0; 
	}
};

__device__ inline bool infect_ppl(
	Person&p, const Person& source, 
	double baseprob, RandGen& rand_obj,
	bool* infect_result){
	
	if(rand_obj.cudaRandUni() < baseprob*p.prs*source.pri){
		infect_result[p.id] = true;
		return true;
	}
	return false;
}

__device__ inline void print_const(dCommunity& comm){
	printf("community %d\n",comm.id);
	printf("daycpcm\n");
	for(int i = 0; i < 5; i++) printf("%f ", comm.daycpcm);
	printf("\n");
}

struct IsInfectiousDay{
	Person* ppl_vec;
	
	IsInfectiousDay(Person* _ppl_vec){ppl_vec = _ppl_vec;}
	
	__device__
	bool operator()(int pid){
		Person p = ppl_vec[pid];
		return isInfectious(p) && !isWithdrawn(p) && 
			!isQuarantined(p) && p.nTravelTimer<=0;
	}
};

struct IsHomeWorkerDay{
	Person* ppl_vec;
	
	IsHomeWorkerDay(Person* _ppl_vec){ppl_vec = _ppl_vec;}
	
	__device__
	bool operator()(int pid){
		Person p = ppl_vec[pid];
		return p.nDayComm==p.nHomeComm;
	}
};

struct IsInfectedDay{
	bool* infect_result;
	IsInfectedDay(bool* _infect_result){infect_result = _infect_result;}

	__device__
	bool operator()(const int& pid){
		return infect_result[pid];
	}
};

struct SortPplByDayComm{
	Person* pvec;
	SortPplByDayComm(Person* _pvec){pvec = _pvec;}

	__device__
	bool operator()(int x, int y){
		return pvec[x].nDayComm < pvec[y].nDayComm;
	}
};



// infect from home and home workers
__device__ inline void dayInfectSusceptibles(
	const Person& infected, dCommunity& comm, // original arguments
	Person* ppl_vec,Tract* tract_vec,dCommunity* comm_vec,RandGen& rand_obj,bool bTravel,
	dCommWorker* comm_worker_vec, unsigned int comm_worker_start_idx, unsigned int comm_worker_end_idx,
	dCommVisitor* comm_visitor_vec, unsigned int comm_visitor_start_idx, unsigned int comm_visitor_end_idx,
	bool* infect_result){
	
	const double *cpf = (isChild(infected) ? dcpfc : dcpfa);
	// infected is at home during the day
	bool bInfectedAtHome = (isWithdrawn(infected)
		|| isQuarantined(infected) || infected.nWorkplace == 0
		|| (isChild(infected) && infected.nWorkplace < 9
		&& isSchoolClosed(tract_vec[infected.nDayTract],
		infected.nWorkplace)));

	// infected's school or playgroup is open (playgroups are always open)
	bool bInfectedAtSchool = (isChild(infected)
		&& infected.nWorkplace > 0
		&& ((infected.age == 0 && infected.nWorkplace >= 9)
		|| !isSchoolClosed(tract_vec[infected.nDayTract],infected.nWorkplace)));

	// infected works during the day
	bool bInfectedAtWork = (isWorkingAge(infected) && infected.nWorkplace > 0);

	double casualmultiplier = 1.0; // casual contacts multiplier for children
	if ((infected.age == 1 || (infected.age == 0 && infected.nWorkplace < 9))
		&& isSchoolClosed(tract_vec[infected.nDayTract],infected.nWorkplace))
		casualmultiplier = 2.0; // casual contacts double for out-of-school children

	for (unsigned int pid2 = comm.nFirstPerson; pid2 < comm.nLastPerson; pid2++) {
		Person &p2 = ppl_vec[pid2];
		
		if (isSusceptible(p2) && p2.nDayComm == comm.id
		&& p2.nTravelTimer <= 0) {
			if (infected.family == p2.family 
			&& bInfectedAtHome// daytime transmission within household
			&& (isQuarantined(p2) || p2.nWorkplace == 0
			|| (isChild(p2) && p2.nWorkplace < 9
			&& isSchoolClosed(tract_vec[comm.nTractID],p2.nWorkplace)))) {
				// susceptible is at home
				if(infect_ppl(p2,infected,cpf[p2.age],rand_obj,infect_result)) 
					continue;
			}
				
			double casualmultiplier2 = casualmultiplier; // casual contacts multiplier for children
			if (casualmultiplier2==1.0 && (p2.age==1 || (p2.age==0 && p2.nWorkplace<9)) 
			&& isSchoolClosed(tract_vec[p2.nDayTract],p2.nWorkplace))
				casualmultiplier2 = 2.0;      // casual contacts double for out-of-school children
									
			if (!isQuarantined(p2) && 
			!infect_ppl(p2,infected,comm.daycpcm[p2.age]*casualmultiplier2,rand_obj,infect_result)) { 
				if (infected.nDayNeighborhood==p2.nDayNeighborhood){ // transmission within neighborhood
					if (infect_ppl(p2,infected,comm.daycpnh[p2.age]*casualmultiplier2,rand_obj,infect_result)) 
						continue;
				}	
				if (isChild(infected)) {  // transmitter is child
					if (bInfectedAtSchool && isChild(p2) && infected.nWorkplace==p2.nWorkplace){
						infect_ppl(p2,infected,(infected.nWorkplace>=9?dcps[9]:comm.cps[infected.nWorkplace]),
							rand_obj,infect_result); 
					}
				} else {           
					// transmitter is adult
					if (bInfectedAtWork && isWorkingAge(p2) && infected.nWorkplace==p2.nWorkplace){
						// transmission within work group
						infect_ppl(p2,infected,0.05,rand_obj,infect_result); 
					}
				}
			}
		}
	} // end big for loop
	
	// infection for outsider workers
	for (unsigned int i = comm_worker_start_idx; i < comm_worker_end_idx; i++) {
		Person &p2 = ppl_vec[comm_worker_vec[i].worker_id];
		if (isSusceptible(p2) && !isQuarantined(p2) && p2.nTravelTimer<=0) {			
			if (!infect_ppl(p2,infected,comm.daycpcm[p2.age]*casualmultiplier,rand_obj,infect_result)) { // transmission within community
				if (infected.nDayNeighborhood==p2.nDayNeighborhood){  // transmission within neighborhood				
					if (infect_ppl(p2,infected,comm.daycpnh[p2.age]*casualmultiplier,rand_obj,infect_result)) 
						continue;
				}
				if (isWorkingAge(infected) && infected.nWorkplace==p2.nWorkplace) {
					// transmit to coworkers from other tracts
					infect_ppl(p2,infected,0.05,rand_obj,infect_result); 
				}
			}
		}
	}
	
	// infect visitors
	if (bTravel){
		for (unsigned int i = comm_visitor_start_idx; i < comm_visitor_end_idx; i++) {
			Person &p2 = comm_visitor_vec[i].visitor;
			if (isSusceptible(p2)) { 	
				if (!infect_ppl(p2,infected,comm.daycpcm[p2.age]*casualmultiplier,rand_obj,infect_result)) { // transmission within community
					if (infected.nDayNeighborhood==p2.nDayNeighborhood){
						if (infect_ppl(p2,infected,comm.daycpnh[p2.age]*casualmultiplier,rand_obj,infect_result))
							continue;
					}
					if (isWorkingAge(infected) && 
						isWorkingAge(p2) && infected.nWorkplace==p2.nWorkplace && p2.nWorkplace>0){
						
						infect_ppl(p2,infected,0.05,rand_obj,infect_result); 
					}	
				}
			}
		}
	}
	
}


// infect from home and home workers
__device__ inline void dayInfectSusceptiblesBetter(
	const Person& infected, dCommunity& comm, // original arguments
	Person* ppl_vec,Tract* tract_vec,dCommunity* comm_vec,RandGen& rand_obj,bool bTravel,
	unsigned firstPerson, unsigned lastPerson,
	dCommWorker* comm_worker_vec, unsigned int comm_worker_start_idx, unsigned int comm_worker_end_idx,
	dCommVisitor* comm_visitor_vec, unsigned int comm_visitor_start_idx, unsigned int comm_visitor_end_idx,
	bool* infect_result){
	
	const double *cpf = (isChild(infected) ? dcpfc : dcpfa);
	// infected is at home during the day
	bool bInfectedAtHome = (isWithdrawn(infected)
		|| isQuarantined(infected) || infected.nWorkplace == 0
		|| (isChild(infected) && infected.nWorkplace < 9
		&& isSchoolClosed(tract_vec[infected.nDayTract],
		infected.nWorkplace)));

	// infected's school or playgroup is open (playgroups are always open)
	bool bInfectedAtSchool = (isChild(infected)
		&& infected.nWorkplace > 0
		&& ((infected.age == 0 && infected.nWorkplace >= 9)
		|| !isSchoolClosed(tract_vec[infected.nDayTract],infected.nWorkplace)));

	// infected works during the day
	bool bInfectedAtWork = (isWorkingAge(infected) && infected.nWorkplace > 0);

	double casualmultiplier = 1.0; // casual contacts multiplier for children
	if ((infected.age == 1 || (infected.age == 0 && infected.nWorkplace < 9))
		&& isSchoolClosed(tract_vec[infected.nDayTract],infected.nWorkplace))
		casualmultiplier = 2.0; // casual contacts double for out-of-school children

	for (unsigned int pid2 = firstPerson; pid2 < lastPerson; pid2++) {
		Person p2 = ppl_vec[pid2];
		
		if (isSusceptible(p2) && p2.nDayComm == comm.id
		&& p2.nTravelTimer <= 0) {
			if (infected.family == p2.family 
			&& bInfectedAtHome// daytime transmission within household
			&& (isQuarantined(p2) || p2.nWorkplace == 0
			|| (isChild(p2) && p2.nWorkplace < 9
			&& isSchoolClosed(tract_vec[comm.nTractID],p2.nWorkplace)))) {
				// susceptible is at home
				if(infect_ppl(p2,infected,cpf[p2.age],rand_obj,infect_result)) 
					continue;
			}
				
			double casualmultiplier2 = casualmultiplier; // casual contacts multiplier for children
			if (casualmultiplier2==1.0 && (p2.age==1 || (p2.age==0 && p2.nWorkplace<9)) 
			&& isSchoolClosed(tract_vec[p2.nDayTract],p2.nWorkplace))
				casualmultiplier2 = 2.0;      // casual contacts double for out-of-school children
									
			if (!isQuarantined(p2) && 
			!infect_ppl(p2,infected,comm.daycpcm[p2.age]*casualmultiplier2,rand_obj,infect_result)) { 
				if (infected.nDayNeighborhood==p2.nDayNeighborhood){ // transmission within neighborhood
					if (infect_ppl(p2,infected,comm.daycpnh[p2.age]*casualmultiplier2,rand_obj,infect_result)) 
						continue;
				}	
				if (isChild(infected)) {  // transmitter is child
					if (bInfectedAtSchool && isChild(p2) && infected.nWorkplace==p2.nWorkplace){
						infect_ppl(p2,infected,(infected.nWorkplace>=9?dcps[9]:comm.cps[infected.nWorkplace]),
							rand_obj,infect_result); 
					}
				} else {           
					// transmitter is adult
					if (bInfectedAtWork && isWorkingAge(p2) && infected.nWorkplace==p2.nWorkplace){
						// transmission within work group
						infect_ppl(p2,infected,0.05,rand_obj,infect_result); 
					}
				}
			}
		}
	} // end big for loop
	
	// infection for outsider workers
	for (unsigned int i = comm_worker_start_idx; i < comm_worker_end_idx; i++) {
		Person p2 = ppl_vec[comm_worker_vec[i].worker_id];
		if (isSusceptible(p2) && !isQuarantined(p2) && p2.nTravelTimer<=0) {			
			if (!infect_ppl(p2,infected,comm.daycpcm[p2.age]*casualmultiplier,rand_obj,infect_result)) { // transmission within community
				if (infected.nDayNeighborhood==p2.nDayNeighborhood){  // transmission within neighborhood				
					if (infect_ppl(p2,infected,comm.daycpnh[p2.age]*casualmultiplier,rand_obj,infect_result)) 
						continue;
				}
				if (isWorkingAge(infected) && infected.nWorkplace==p2.nWorkplace) {
					// transmit to coworkers from other tracts
					infect_ppl(p2,infected,0.05,rand_obj,infect_result); 
				}
			}
		}
	}
	
	// infect visitors
	if (bTravel){
		for (unsigned int i = comm_visitor_start_idx; i < comm_visitor_end_idx; i++) {
			Person p2 = comm_visitor_vec[i].visitor;
			if (isSusceptible(p2)) { 	
				if (!infect_ppl(p2,infected,comm.daycpcm[p2.age]*casualmultiplier,rand_obj,infect_result)) { // transmission within community
					if (infected.nDayNeighborhood==p2.nDayNeighborhood){
						if (infect_ppl(p2,infected,comm.daycpnh[p2.age]*casualmultiplier,rand_obj,infect_result))
							continue;
					}
					if (isWorkingAge(infected) && 
						isWorkingAge(p2) && infected.nWorkplace==p2.nWorkplace && p2.nWorkplace>0){
						
						infect_ppl(p2,infected,0.05,rand_obj,infect_result); 
					}	
				}
			}
		}
	}
}


// calculate daytime susceptiblity and infectious for each visitors
// struct DayOp2{
	// double AVEs,AVEi;

	// DayOp2(double _AVEs, double _AVEi){
		// AVEs = _AVEs; AVEi = _AVEi;
	// }

	// __device__ 
	// void operator()(dCommVisitor& comm_visitor){
		// Person &p = comm_visitor.visitor;
		// p.prs = 1.0 - p.fBaselineVES;
		// if(isInfectious(p)) 
			// p.pri = dvload[p.nWhichVload][(int)(p.iday)]; 
		// else 
			// p.pri = 0.0;
		// if(isVaccinated(p)){
			// if (needsBoost(p)){
			// p.prs *= (1.0-dVaccineData[whichVaccine(p)].VEs*
				// ddefaultvacceff[p.vday]*dfVaccineEfficacyByAge[p.age]);
			// p.pri *= (1.0-dVaccineData[whichVaccine(p)].VEi*
				// ddefaultvacceff[p.vday]*dfVaccineEfficacyByAge[p.age]);
      			// } else {
			// p.prs *= (1.0-dVaccineData[whichVaccine(p)].VEs*
				// dVaccineData[whichVaccine(p)].vacceff[p.vday]*
				// dfVaccineEfficacyByAge[p.age]);
			// p.pri *= (1.0-dVaccineData[whichVaccine(p)].VEi*
				// dVaccineData[whichVaccine(p)].vacceff[p.vday]*
				// dfVaccineEfficacyByAge[p.age]);
      			// }
		// }
		// if(isAntiviral(p)){
			// p.prs *= (1.0-AVEs);
      			// p.pri *= (1.0-AVEi);
		// }
		// if (isSymptomatic(p)) p.pri *= 2.0; 
	// }		
// };	
	
	
struct DayOp3Better{
	Person* ppl_vec;
	Tract* tract_vec;
	dCommunity* comm_vec;
	RandGen* rand_vec;
	bool bTravel;

	dCommWorker* comm_worker_vec; 
	unsigned int* comm_worker_start_vec;
	unsigned int* comm_worker_end_vec;
	dCommVisitor* comm_visitor_vec;
	unsigned int* comm_visitor_start_vec;
	unsigned int* comm_visitor_end_vec;
	
	bool* infect_result;
	
	bool isHomeInfect;
	
	DayOp3Better(Person* _ppl_vec, Tract* _tract_vec,dCommunity* _comm_vec,RandGen* _rand_vec,bool _bTravel,
		dCommWorker* _comm_worker_vec, unsigned int* _comm_worker_start_vec, unsigned int* _comm_worker_end_vec,
		dCommVisitor* _comm_visitor_vec, unsigned int* _comm_visitor_start_vec, unsigned int* _comm_visitor_end_vec,
		bool* _infect_result, bool _isHomeInfect){
		
		ppl_vec = _ppl_vec; 
		tract_vec = _tract_vec;
		comm_vec = _comm_vec;
		rand_vec = _rand_vec;
		bTravel = _bTravel;
		
		comm_worker_vec = _comm_worker_vec; 
		comm_worker_start_vec = _comm_worker_start_vec;
		comm_worker_end_vec = _comm_worker_end_vec;
		comm_visitor_vec = _comm_visitor_vec;
		comm_visitor_start_vec = _comm_visitor_start_vec;
		comm_visitor_end_vec = _comm_visitor_end_vec;
		
		infect_result = _infect_result;
		isHomeInfect = _isHomeInfect;
	}

	__device__ 
	void operator()(int& pid){
		int idx = threadIdx.x + blockIdx.x * blockDim.x;
	
		Person& p = ppl_vec[pid]; 
		RandGen rand_obj = rand_vec[idx];
		
		int comm_id = isHomeInfect ? p.nHomeComm : p.nDayComm;
		
		dCommunity& comm = comm_vec[comm_id];
		unsigned int comm_worker_start_idx = comm_worker_start_vec[comm_id];
		unsigned int comm_worker_end_idx = comm_worker_end_vec[comm_id];
		unsigned int comm_visitor_start_idx = comm_visitor_start_vec[comm_id];
		unsigned int comm_visitor_end_idx = comm_visitor_end_vec[comm_id];
		
		dayInfectSusceptibles(p,comm,ppl_vec,tract_vec,comm_vec,rand_obj,bTravel,
			comm_worker_vec,comm_worker_start_idx,comm_worker_end_idx,
			comm_visitor_vec,comm_visitor_start_idx,comm_visitor_end_idx,
			infect_result);
			
		rand_vec[idx] = rand_obj;
	}
};


__global__ inline void DayOp3EvenBetter(int* personIdxVec, Person* ppl_vec, Tract* tract_vec,dCommunity* comm_vec,RandGen* rand_vec,bool bTravel,
		dCommWorker* comm_worker_vec, unsigned int* comm_worker_start_vec, unsigned int* comm_worker_end_vec,
		dCommVisitor* comm_visitor_vec, unsigned int* comm_visitor_start_vec, unsigned int* comm_visitor_end_vec,
		bool* infect_result, bool isHomeInfect){

		int bid = blockIdx.x;
		int tid = threadIdx.x;
		int num_threads = blockDim.x;
		int idx = tid + bid * num_threads;

		Person p = ppl_vec[personIdxVec[bid]];
		RandGen rand_obj = rand_vec[idx];
		int comm_id = isHomeInfect ? p.nHomeComm : p.nDayComm;

		dCommunity& comm = comm_vec[comm_id];
		unsigned int comm_first_person_idx_blk = comm.nFirstPerson;
		unsigned int comm_last_person_idx_blk = comm.nLastPerson;
		unsigned int comm_worker_start_idx_blk = comm_worker_start_vec[comm_id];
		unsigned int comm_worker_end_idx_blk = comm_worker_end_vec[comm_id];
		unsigned int comm_visitor_start_idx_blk = comm_visitor_start_vec[comm_id];
		unsigned int comm_visitor_end_idx_blk = comm_visitor_end_vec[comm_id];

		int size1 = (comm_last_person_idx_blk - comm_first_person_idx_blk);
		int size2 = (comm_worker_end_idx_blk - comm_worker_start_idx_blk);
		int size3 = (comm_visitor_end_idx_blk - comm_visitor_start_idx_blk);

		int thread_size1 = size1 / num_threads + (size1 % num_threads != 0);
		int thread_size2 = size2 / num_threads + (size2 % num_threads != 0);
		int thread_size3 = size3 / num_threads + (size3 % num_threads != 0);

		unsigned int comm_first_person_idx = comm_first_person_idx_blk + tid * thread_size1;
		unsigned int comm_last_person_idx = comm_first_person_idx + thread_size1;
		unsigned int comm_worker_start_idx = comm_worker_start_idx_blk + tid * thread_size2;
		unsigned int comm_worker_end_idx = comm_worker_start_idx + thread_size2;
		unsigned int comm_visitor_start_idx = comm_visitor_start_idx_blk + tid * thread_size3;
		unsigned int comm_visitor_end_idx = comm_visitor_start_idx + thread_size3;

		comm_last_person_idx = comm_last_person_idx < comm_last_person_idx_blk ? comm_last_person_idx : comm_last_person_idx_blk;
		comm_worker_end_idx = comm_worker_end_idx < comm_worker_end_idx_blk ? comm_worker_end_idx : comm_worker_end_idx_blk;
		comm_visitor_end_idx = comm_visitor_end_idx < comm_visitor_end_idx_blk ? comm_visitor_end_idx : comm_visitor_end_idx_blk;

		
		dayInfectSusceptiblesBetter(p,comm,ppl_vec,tract_vec,comm_vec,rand_obj,bTravel,
			comm_first_person_idx,comm_last_person_idx,
			comm_worker_vec,comm_worker_start_idx,comm_worker_end_idx,
			comm_visitor_vec,comm_visitor_start_idx,comm_visitor_end_idx,
			infect_result);
			
		rand_vec[idx] = rand_obj;

};



struct DayOp3{
	Person* ppl_vec;
	Tract* tract_vec;
	dCommunity* comm_vec;
	RandGen* rand_vec;
	bool bTravel;
	
	dCommWorker* comm_worker_vec; 
	unsigned int* comm_worker_start_vec;
	unsigned int* comm_worker_end_vec;
	dCommVisitor* comm_visitor_vec;
	unsigned int* comm_visitor_start_vec;
	unsigned int* comm_visitor_end_vec;
	
	bool* infect_result;
	
	DayOp3(Person* _ppl_vec, Tract* _tract_vec,dCommunity* _comm_vec,RandGen* _rand_vec,bool _bTravel,
		dCommWorker* _comm_worker_vec, unsigned int* _comm_worker_start_vec, unsigned int* _comm_worker_end_vec,
		dCommVisitor* _comm_visitor_vec, unsigned int* _comm_visitor_start_vec, unsigned int* _comm_visitor_end_vec,
		bool* _infect_result){
		
		ppl_vec = _ppl_vec; 
		tract_vec = _tract_vec;
		comm_vec = _comm_vec;
		rand_vec = _rand_vec;
		bTravel = _bTravel;
		
		comm_worker_vec = _comm_worker_vec; 
		comm_worker_start_vec = _comm_worker_start_vec;
		comm_worker_end_vec = _comm_worker_end_vec;
		comm_visitor_vec = _comm_visitor_vec;
		comm_visitor_start_vec = _comm_visitor_start_vec;
		comm_visitor_end_vec = _comm_visitor_end_vec;
		
		infect_result = _infect_result;
	}	

	__device__
	void operator()(dCommunity& comm){

		unsigned int comm_worker_start_idx = comm_worker_start_vec[comm.id];
		unsigned int comm_worker_end_idx = comm_worker_end_vec[comm.id];
		unsigned int comm_visitor_start_idx = comm_visitor_start_vec[comm.id];
		unsigned int comm_visitor_end_idx = comm_visitor_end_vec[comm.id];
	
		RandGen rand_obj = rand_vec[comm.id];
	
		if (comm.ninf[0]>0 || comm.ninf[1]>0 || comm.ninf[2]>0 || comm.ninf[3]>0 || comm.ninf[4]>0){
			for(unsigned int pid = comm.nFirstPerson; pid < comm.nLastPerson; pid++){
				Person &p = ppl_vec[pid];
				
				if (isInfectious(p) && !isWithdrawn(p) && 
				!isQuarantined(p) && p.nDayComm==comm.id && p.nTravelTimer<=0) {

					dayInfectSusceptibles(p,comm,ppl_vec,tract_vec,comm_vec,rand_obj,bTravel,
						comm_worker_vec,comm_worker_start_idx,comm_worker_end_idx,
						comm_visitor_vec,comm_visitor_start_idx,comm_visitor_end_idx,
						infect_result);
				}
			}			
		}
		
		
		for (unsigned int i = comm_worker_start_idx; i < comm_worker_end_idx; i++){
			Person &p = ppl_vec[comm_worker_vec[i].worker_id];
			if (isInfectious(p) && !isWithdrawn(p) && 
				!isQuarantined(p) && p.nTravelTimer<=0)
					dayInfectSusceptibles(p,comm,ppl_vec,tract_vec,comm_vec,rand_obj,bTravel,
						comm_worker_vec,comm_worker_start_idx,comm_worker_end_idx,
						comm_visitor_vec,comm_visitor_start_idx,comm_visitor_end_idx,
						infect_result);
		}
		
		if (bTravel) {
			for (unsigned int i = comm_visitor_start_idx; i < comm_visitor_end_idx; i++) {
				Person &p = comm_visitor_vec[i].visitor;
				if (isInfectious(p) && !isWithdrawn(p)) // && !isQuarantined(p))
					dayInfectSusceptibles(p,comm,ppl_vec,tract_vec,comm_vec,rand_obj,bTravel,
						comm_worker_vec,comm_worker_start_idx,comm_worker_end_idx,
						comm_visitor_vec,comm_visitor_start_idx,comm_visitor_end_idx,
						infect_result);
			}
		}
		
		rand_vec[comm.id] = rand_obj;
	}
};

/*********** night functions *************/



typedef thrust::device_vector<Person> dPersonVec;
typedef thrust::device_vector<dCommunity> dCommunityVec;
typedef thrust::device_vector<dCommWorker> dCommWorkerVec;
typedef thrust::device_vector<dCommVisitor> dCommVisitorVec;
typedef thrust::device_vector<Tract> dTractVec;

typedef thrust::host_vector<dCommWorker> hCommWorkerVec;
typedef thrust::host_vector<dCommVisitor> hCommVisitorVec;

typedef thrust::device_vector<unsigned int> dIdxVec;

typedef thrust::device_vector<RandGen> dRandVec;
typedef thrust::host_vector<RandGen> hRandVec;



#endif
